package com.allenja.eleviewer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.opengl.GLSurfaceView;
import android.util.FloatMath;
import android.view.MotionEvent;

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Logger;
import com.threed.jpct.TextureManager;
import com.threed.jpct.Virtualizer;
import com.threed.jpct.RGBColor;
import com.threed.jpct.Texture;
import com.threed.jpct.World;

@SuppressLint("FloatMath")
public class MyRenderer implements GLSurfaceView.Renderer, SensorEventListener {
	// renderer
	private FrameBuffer fb = null;
	private World  world;
	private RGBColor back = null;
	private Virtualizer virtualizer = new Virtualizer();
	private TextureManager tm = TextureManager.getInstance();
	private boolean stop = false;
	private static String modelViewMode = "Polygons";
	// frames per second
	private long time = System.currentTimeMillis();
	private int fps = 0;
	private int lfps = 0;
	// touch control
	private int touchMode = 0;
	private final int NONE = 0;
	private final int DRAG = 0;
	private final int ZOOM = 0;
	private float oldDistance = 250;
	private float newDistance = 0;
	private int tapCount = 0;
	private long tapStartTime = 0;
	private long tapDuration = 0;
	// sensor control
	private SensorManager sensorManager;
	private Sensor accelSensor = null;
	private Sensor magSensor = null;
	private float[] rotationMatrix = new float[9];
	private float[] accelData = new float[3];
	private float[] bufferedAccelData = new float[3];
	private float[] magnetData = new float[3];
	private float[] bufferedMagnetData = new float[3];
	final static float rad2deg = (float) (180.0f / Math.PI);
	// CONSTANTS
	private static final double MODEL_ORIGIN_X = 145.66726645776;// srtm 145.62583361 //JCU 145.66726645776;
	private static final double MODEL_ORIGIN_Y = -16.8281367352939;// srtm -17.0313619906 // JCU -16.8281367352939;
	//private static final float MAX_Y = 3000f;
	//private static final float MAX_Z = 555.413025f; // srtm 1314 // JCU 555.413025f;
	//private static final float MIN_Z = 2.7523f; // srtm -15 // JCU 2.7523f;
	private static final int MAX_TAP_DURATION = 500;
	private static final double SCALE = 0.00000927; // srtm 30m = 0.00027778 // JCU 1m = 0.00000927; // DD to metre
	// gps control
	private Location currentLocation = null;
	private boolean currentLocationHasChanged = false;
	// variables for gps mode
	private float newCamX = 0;
	private float newCamY = 0;
	private float newCamZ = 0;
	private Object rendererLocationMode;

	public MyRenderer(Context context) {		
		// initialise accelerometer and magnet sensor
		setSensorManager((SensorManager) context.getSystemService(Context.SENSOR_SERVICE));
		accelSensor = getSensorManager().getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		magSensor = getSensorManager().getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
		
		// configuration for OpenGL
		Config.maxPolysVisible = 100000;
		Config.farPlane = 1000000;
		Config.glTransparencyMul = 0.1f;
		Config.glTransparencyOffset = 0.1f;
		Config.useVBO = true;
		
		// configuration for textures
		Texture.defaultToMipmapping(true);
		Texture.defaultTo4bpp(true);
		tm.setVirtualizer(virtualizer);
		virtualizer.setContext(context);
	}

	public void stop() {
		stop = true;
		
		// unregister the sensors
		sensorManager.unregisterListener(this);
	}
	
	public void pause() {
		
	}
	
	public void resume() {
		stop = false;
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		Logger.log("MyRenderer surface created");
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		// dispose framebuffer if not null
		if (fb != null) {
			fb.dispose();
		}
		
		// create new framebuffer
		fb = new FrameBuffer(w, h);
		
		if (modelViewMode.equals("Polygons")) {
			
			// check if textures are already in the texture manager and add if not
			if (!tm.containsTexture("esri_color_scale")) {
				tm.addTexture("esri_color_scale", CameraViewActivity.esri_color_scale);
			}
			
			tm.virtualize(CameraViewActivity.esri_color_scale);
			
			//world = CameraViewActivity.getWorld();
			
			// register the sensors
			getSensorManager().registerListener(this, accelSensor, SensorManager.SENSOR_DELAY_GAME);
			getSensorManager().registerListener(this, magSensor,	SensorManager.SENSOR_DELAY_GAME);
		}
	}


	public void onDrawFrame(GL10 gl) {
		try {
			if (!stop) {
				
				// set background color and alpha
				back = new RGBColor(0, 0, 0, 0); // black with alpha
				
				// smooth sensor data
				rootMeanSquareBuffer(bufferedAccelData, accelData);
				rootMeanSquareBuffer(bufferedMagnetData, magnetData);
				
				// get rotation matrix from device and set camera orientation accordingly
				boolean success = SensorManager.getRotationMatrix(rotationMatrix, null, bufferedAccelData, bufferedMagnetData);
				if (success) {
					setCamBackBuffer();
				}

				fb.clear(back);
				world.renderScene(fb);
				if (fb.isInitialized()) {
					Logger.log("FrameBuffer is OK");
				}
				world.draw(fb);

				if (rendererLocationMode.equals("GPS")) {
					// Logger.log("GPS mode");

					if (currentLocation != null || currentLocationHasChanged) {
						
						// assign new camera X and Y from gps 
						newCamX = (float) ((currentLocation.getLongitude() - MODEL_ORIGIN_X) / SCALE);
						newCamY = (float) ((currentLocation.getLatitude() - MODEL_ORIGIN_Y) / SCALE);
						newCamZ = (float) currentLocation.getAltitude();
						
						// set new camera position
						CameraViewActivity.getWorldCam().setPosition(newCamX, newCamY, newCamZ);
						
						// draw world and Decimal Degree coordinates to the screen
						String newCamXString = String.valueOf(newCamX);
						String[] newCamXStringParts = newCamXString.split("\\.");
						String newCamXStringDegree = newCamXStringParts[0];
						String newCamXStringDecimalDegree = newCamXStringParts[1];

						blitNumber(Integer.parseInt(newCamXStringDegree), 50, 5);
						blitNumber(Integer.parseInt(newCamXStringDecimalDegree), 100, 5);

						String newCamYString = String.valueOf(newCamY);
						String[] newCamYStringParts = newCamYString.split("\\.");
						String newCamYStringDegree = newCamYStringParts[0];
						String newCamYStringDecimalDegree = newCamYStringParts[1];

						blitNumber(Integer.parseInt(newCamYStringDegree), 175, 5);
						blitNumber(Integer.parseInt(newCamYStringDecimalDegree), 225, 5);

						blitNumber((int) newCamZ, 300, 5);

						String currentLongitudeString = String.valueOf(currentLocation.getLongitude());
						String[] currentLongitudeStringParts = currentLongitudeString.split("\\.");
						String currentLongitudeStringDegree = currentLongitudeStringParts[0];
						String currentLongitudeStringDecimalDegree = currentLongitudeStringParts[1];

						blitNumber(Integer.parseInt(currentLongitudeStringDegree), 375, 5);
						blitNumber(Integer.parseInt(currentLongitudeStringDecimalDegree), 425, 5);

						String currentLatitudeString = String.valueOf(currentLocation.getLatitude());
						String[] currentLatitudeStringParts = currentLatitudeString.split("\\.");
						String currentLatitudeStringDegree = currentLatitudeStringParts[0];
						String currentLatitudeStringDecimalDegree = currentLatitudeStringParts[1];

						blitNumber(Integer.parseInt(currentLatitudeStringDegree), 500, 5);
						blitNumber(Integer.parseInt(currentLatitudeStringDecimalDegree), 550, 5);
						
						currentLocationHasChanged = false;
					} 
					
					else if (currentLocation == null) {
						
						// set camera position to model origin and 800m
						//newCamX = CameraViewActivity.getWorld().getObject(0).getOrigin().x;
						//newCamY = CameraViewActivity.getWorld().getObject(0).getOrigin().y;
						newCamZ = 800f;

						CameraViewActivity.getWorldCam().setPosition(newCamX, newCamY, newCamZ);
						
						// draw world coordinates to the screen
						String camXString = String.valueOf(newCamX);
						String[] camXStringParts = camXString.split("\\.");
						String camXStringDegree = camXStringParts[0];
						String camXStringDecimalDegree = camXStringParts[1];

						blitNumber(Integer.parseInt(camXStringDegree), 50, 5);
						blitNumber(Integer.parseInt(camXStringDecimalDegree), 100, 5);

						String camYString = String.valueOf(newCamY);
						String[] camYStringParts = camYString.split("\\.");
						String camYStringDegree = camYStringParts[0];
						String camYStringDecimalDegree = camYStringParts[1];

						blitNumber(Integer.parseInt(camYStringDegree), 175, 5);
						blitNumber(Integer.parseInt(camYStringDecimalDegree), 225, 5);

						blitNumber((int) newCamZ, 300, 5);
					}
				} 
				
				else if (rendererLocationMode.equals("Explorer")) {
					// Logger.log("Explorer mode");
					
					// draw world coordinates to the screen
					String camXString = String.valueOf(CameraViewActivity.getWorldCam().getPosition().x);
					String[] camXStringParts = camXString.split("\\.");
					String camXStringDegree = camXStringParts[0];
					String camXStringDecimalDegree = camXStringParts[1];

					blitNumber(Integer.parseInt(camXStringDegree), 50, 5);
					blitNumber(Integer.parseInt(camXStringDecimalDegree), 100, 5);

					String camYString = String.valueOf(CameraViewActivity.getWorldCam().getPosition().y);
					String[] camYStringParts = camYString.split("\\.");
					String camYStringDegree = camYStringParts[0];
					String camYStringDecimalDegree = camYStringParts[1];

					blitNumber(Integer.parseInt(camYStringDegree), 175, 5);
					blitNumber(Integer.parseInt(camYStringDecimalDegree), 225, 5);

					blitNumber((int) CameraViewActivity.getWorldCam().getPosition().z, 300, 5);
				}
				
				// draw frame rate to the screen
				blitNumber(lfps, 5, 5);
				fb.display();
				
				// update frame rate
				if (System.currentTimeMillis() - time >= 1000) {
					lfps = fps;
					fps = 0;
					time = System.currentTimeMillis();
				}
				fps++;
			} 
			
			else {
				if (fb != null) {
					fb.dispose();
					fb = null;
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
			Logger.log("Drawing thread terminated!", Logger.MESSAGE);
		}
	}

	private void setCamBackBuffer() {		
		// set camera back buffer to current device orientation
		float[] result = new float[9];
		SensorManager.remapCoordinateSystem(rotationMatrix,	SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_MINUS_X, result);
		com.threed.jpct.Matrix mResult = new com.threed.jpct.Matrix();
		copyMatrix(result, mResult);
		CameraViewActivity.getWorldCam().setBack(mResult);
	}

	private void blitNumber(int number, int x, int y) {		
		// get number from numbers.jpg (font) and draw to screen
		if (CameraViewActivity.getFont() != null) {
			String sNum = Integer.toString(number);

			for (int i = 0; i < sNum.length(); i++) {
				char cNum = sNum.charAt(i);
				int iNum = cNum - 48;
				//fb.blit(font, iNum * 5, 0, x, y, 5, 9, FrameBuffer.TRANSPARENT_BLITTING);				
				
				fb.blit(CameraViewActivity.getFont(), iNum * 5, 0, x, y, 5, 9, 10, 18, 100000, false);
				x += 10;
			}
		}
	}

	private void copyMatrix(float[] src, com.threed.jpct.Matrix dest) {		
		// copy the rotation matrix from sensors
		dest.setRow(0, src[0], src[1], src[2], 0);
		dest.setRow(1, src[3], src[4], src[5], 0);
		dest.setRow(2, src[6], src[7], src[8], 0);
		dest.setRow(3, 0f, 0f, 0f, 1f);
	}

	public void handleGLSurfaceTouchEvent(MotionEvent event) {		
		// handle touch events
		int action = event.getAction();

		switch (action & MotionEvent.ACTION_MASK) {

		case MotionEvent.ACTION_DOWN:
			tapStartTime = System.currentTimeMillis();
			tapCount++;
			touchMode = DRAG;
			break;

		case MotionEvent.ACTION_POINTER_DOWN:
			oldDistance = getTouchSpacing(event);

			if (oldDistance > 0) {
				touchMode = ZOOM;
			}
			break;

		case MotionEvent.ACTION_UP:
			long time = System.currentTimeMillis() - tapStartTime;
			tapDuration = tapDuration + time;
			if (tapCount == 2 && tapDuration <= MAX_TAP_DURATION) {
				tapCount = 0;
				tapDuration = 0;
				tapStartTime = 0;
				//CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorld().getObject(0).getOrigin().x, CameraViewActivity.getWorld().getObject(0).getOrigin().y, 2000f);
			}
			
			else if (tapCount > 2) {
				tapCount = 0;
				tapDuration = 0;
				tapStartTime = 0;
			}
			
			touchMode = NONE;
			oldDistance = 250;
			break;

		case MotionEvent.ACTION_POINTER_UP:
			touchMode = NONE;
			oldDistance = 250;
			break;

		case MotionEvent.ACTION_MOVE:
			if (event.getPointerCount() > 1 && touchMode == ZOOM) {
				newDistance = getTouchSpacing(event);
				
				if (oldDistance < newDistance && rendererLocationMode.equals("Explorer")) {
					if (CameraViewActivity.getWorldCam().getPosition().z >= 100 && CameraViewActivity.getWorldCam().getPosition().z <= 5000) {
						CameraViewActivity.getWorldCam().moveCamera(Camera.CAMERA_MOVEIN, 25);
					}

					else if (CameraViewActivity.getWorldCam().getPosition().z < 100) {
						CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 100);
					}

					else if (CameraViewActivity.getWorldCam().getPosition().z > 5000) {
						CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 5000);
					}
				}

				else if (oldDistance > newDistance && rendererLocationMode.equals("Explorer")) {
					if (CameraViewActivity.getWorldCam().getPosition().z >= 100 && CameraViewActivity.getWorldCam().getPosition().z <= 5000) {
						CameraViewActivity.getWorldCam().moveCamera(Camera.CAMERA_MOVEOUT, 25);
					}

					else if (CameraViewActivity.getWorldCam().getPosition().z < 100) {
						CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 100);
					}

					else if (CameraViewActivity.getWorldCam().getPosition().z > 5000) {
						CameraViewActivity.getWorldCam().setPosition(CameraViewActivity.getWorldCam().getPosition().x, CameraViewActivity.getWorldCam().getPosition().y, 5000);
					}
				}
			}

			else if (touchMode == DRAG) {
				break;
			}
		}
	}

	public void handleLocationChanged(Location location) {		
		// handle gps location changed
		currentLocation = location;
		currentLocationHasChanged = true;
		//Logger.log("Current location in MyRenderer: " + currentLocation);
	}

	private float getTouchSpacing(MotionEvent event) {		
		// get the distance between fingers when touched
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return FloatMath.sqrt(x * x + y * y);
	}

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// handle sensors accuracy changed
		// Logger.log("onAccuracyChanged: MyRenderer");
	}

	public void onSensorChanged(SensorEvent event) {
		// handle sensor changed
		loadNewSensorData(event);
	}

	private void loadNewSensorData(SensorEvent event) {
		// clone sensor data
		final int type = event.sensor.getType();

		switch (type) {
		
		case Sensor.TYPE_ACCELEROMETER:
			accelData = event.values.clone();
			break;

		case Sensor.TYPE_MAGNETIC_FIELD:
			magnetData = event.values.clone();
			break;
		}
	}

	private void rootMeanSquareBuffer(float[] target, float[] values) {
		// smooth sensor data
		final float amplification = 200.0f;
		float buffer = 20.0f;

		target[0] += amplification;
		target[1] += amplification;
		target[2] += amplification;
		values[0] += amplification;
		values[1] += amplification;
		values[2] += amplification;

		target[0] = FloatMath.sqrt((target[0] * target[0] * buffer + values[0]
				* values[0])
				/ (1 + buffer));
		target[1] = FloatMath.sqrt((target[1] * target[1] * buffer + values[1]
				* values[1])
				/ (1 + buffer));
		target[2] = FloatMath.sqrt((target[2] * target[2] * buffer + values[2]
				* values[2])
				/ (1 + buffer));

		target[0] -= amplification;
		target[1] -= amplification;
		target[2] -= amplification;
		values[0] -= amplification;
		values[1] -= amplification;
		values[2] -= amplification;
	}
	
	public static void setModelViewMode(String mode) {
		modelViewMode = mode;
	}

	public SensorManager getSensorManager() {
		return sensorManager;
	}

	public void setSensorManager(SensorManager sensorManager) {
		this.sensorManager = sensorManager;
	}
}
